#include "stdafx.h"	
#include "../sgp/InputManager.h"

namespace ja2{ namespace sgp
{
	InputManager *InputManager::m_pInstance = nullptr;

	InputManager::InputManager(HWND Window)
		: m_Window(Window),
		m_InputReceived(false),
		m_TrackMousePos(false),
		m_LeftButtonState(false),
		m_RightButtonState(false)
	{
		m_pInstance = this;
		m_MouseHook = SetWindowsHookEx(WH_MOUSE, InputManager::mouse_handler, 0, GetCurrentThreadId());
		JA2_DEBUG_MESSAGE(TOPIC_INPUT, DebugManager::DL_2, (boost::format("Set mouse hook returned %p") % static_cast<void *>(m_MouseHook)).str().c_str());
	}

	Point InputManager::cursor_position() const
	{
		POINT mouse_pos;
		// Get current mouse position
		GetCursorPos(&mouse_pos);
		// In window coords!
		ScreenToClient(m_Window, &mouse_pos);

		return Point(mouse_pos.x, mouse_pos.y);
	}

	bool InputManager::left_button_down() const
	{
		return m_LeftButtonState;
	}

	bool InputManager::right_button_down() const
	{
		return m_RightButtonState;
	}

	void InputManager::queue_event(InputAtom::EventType InputEvent, std::int_least16_t MouseX, std::int_least16_t MouseY, bool IsShiftDown, bool IsCtrlDown, bool IsAltDown, std::uint_least16_t WheelDelta)
	{
		JA2_ASSERT(InputEvent != InputAtom::ET_NONE);

		static uint_least16_t const BUTTON_REPEAT_TIMEOUT = 250;
		static uint_least16_t const DBL_CLK_TIME = 300;

		static std::uint_least32_t left_button_repeat_timer = 0;
		static std::uint_least32_t right_button_repeat_timer = 0;
		static std::uint_least32_t single_click_timer = 0;
		std::uint_least32_t const timer = GetTickCount();

		// Can we queue up one more event, if not, the event is lost forever
		if(m_EventQueue.size() == 256)
			return;

		switch(InputEvent)
		{
		case InputAtom::ET_LEFT_BUTTON_DOWN:
			left_button_repeat_timer = timer + BUTTON_REPEAT_TIMEOUT;
			break;
		case InputAtom::ET_RIGHT_BUTTON_DOWN:
			right_button_repeat_timer = timer + BUTTON_REPEAT_TIMEOUT;
			break;
		case InputAtom::ET_LEFT_BUTTON_UP:
			left_button_repeat_timer = 0;
			// Do we have a double click
			if ( (timer - single_click_timer ) < DBL_CLK_TIME )
			{
				single_click_timer = 0;
				// Add a button up first
				m_EventQueue.push(InputAtom(timer, InputAtom::ET_LEFT_BUTTON_UP, MouseX, MouseY, IsShiftDown, IsCtrlDown, IsAltDown, WheelDelta));
				// Now do double click
				m_EventQueue.push(InputAtom(timer, InputAtom::ET_LEFT_BUTTON_DBL_CLK, MouseX, MouseY, IsShiftDown, IsCtrlDown, IsAltDown, WheelDelta));
			}
			// Save time
			else
				single_click_timer = timer;
			break;
		case InputAtom::ET_RIGHT_BUTTON_UP:
			right_button_repeat_timer = 0;
			break;
		default:
			// Okey Dokey, we can queue up the event, so we do it
			m_EventQueue.push(InputAtom(timer, InputEvent, MouseX, MouseY, IsShiftDown, IsCtrlDown, IsAltDown, WheelDelta));
			break;
		}
	}

	LRESULT InputManager::mouse_handler(int Code, WPARAM wParam, LPARAM lParam)
	{
		// Do not handle this message, pass it on to another window
		if (Code < 0)
			return CallNextHookEx(m_pInstance->m_MouseHook, Code, wParam, lParam);
		// "Unpack" params
		MOUSEHOOKSTRUCT *p_mouse_hook = reinterpret_cast<MOUSEHOOKSTRUCT *>(lParam);
		// Get mouse position
		POINT mouse_pos =  p_mouse_hook->pt;
		// Update the current mouse position
		ScreenToClient(m_pInstance->m_Window, &mouse_pos);
		// Set new mouse position
		std::int_least16_t const mouse_pos_x = static_cast<std::int_least16_t>(mouse_pos.x);
		std::int_least16_t const mouse_pos_y = static_cast<std::int_least16_t>(mouse_pos.y);
		unsigned short const msb_short_bit = 1 << (sizeof(short) * 8 - 1);
		bool const is_shift_down = ((GetAsyncKeyState(VK_SHIFT) & msb_short_bit) != 0);
		bool const is_ctrl_down = ((GetAsyncKeyState(VK_CONTROL) & msb_short_bit) != 0);
		bool const is_alt_down = ((GetAsyncKeyState(VK_MENU) & msb_short_bit) != 0);
		// Input was received
		m_pInstance->m_InputReceived = true;
		// Wheel delta
		std::uint_least16_t delta = 0;
		// Type of event
		InputAtom::EventType type = InputAtom::ET_NONE;
		// Should queue event
		bool should_queue_event = true;
		switch (wParam)
		{
		case WM_LBUTTONDOWN :
			// Trigger an input event
			m_pInstance->m_LeftButtonState = true;
			type = InputAtom::ET_LEFT_BUTTON_DOWN;
			break;
		case WM_LBUTTONUP :
			// Trigger an input event
			m_pInstance->m_LeftButtonState = false;
			type = InputAtom::ET_LEFT_BUTTON_UP;
			break;
		case WM_RBUTTONDOWN :
			// Trigger an input event
			m_pInstance->m_RightButtonState = true;
			type = InputAtom::ET_RIGHT_BUTTON_DOWN;
			break;
		case WM_RBUTTONUP :
			// Trigger an input event
			m_pInstance->m_RightButtonState = false;
			type = InputAtom::ET_RIGHT_BUTTON_UP;
			break;
		case WM_MOUSEWHEEL :
			// Update mouse wheel delta value
			delta = HIWORD(reinterpret_cast<MOUSEHOOKSTRUCTEX *>(lParam)->mouseData) / WHEEL_DELTA;
			// Trigger an input event
			type = InputAtom::ET_MOUSE_WHEEL;
			break;
		case WM_MOUSEMOVE :
			type = InputAtom::ET_MOUSE_POS;
			// Trigger an input event
			should_queue_event = m_pInstance->m_TrackMousePos;
			break;
		default :
			// No input received
			should_queue_event = m_pInstance->m_InputReceived = false;
			break;
		}
		if(should_queue_event)
			m_pInstance->queue_event(type, mouse_pos_x, mouse_pos_y, is_shift_down, is_ctrl_down, is_alt_down, delta);

		return CallNextHookEx(m_pInstance->m_MouseHook, Code, wParam, lParam);
	}
} /*sgp*/} /*ja2*/